#ifndef SHADER_PROGRAM_H
#define SHADER_PROGRAM_H

#include "OpenGL.h"
#include <string>
using std::string;
#include <map>

#include <glm/glm.hpp>
using glm::vec2;
using glm::vec3;
using glm::vec4;
using glm::mat4;
using glm::mat3;

#include <stdexcept>

namespace pancake
{
	class GLProgramException : public std::runtime_error
	{
	public:
		GLProgramException(const string& message) :
				runtime_error(message) { }
	};

	enum  GLShaderType {
		VERTEX= GL_VERTEX_SHADER,
		FRAGMENT= GL_FRAGMENT_SHADER,
		TESS_CONTROL= GL_TESS_CONTROL_SHADER,
		TESS_EVALUATION= GL_TESS_EVALUATION_SHADER,
		GEOMETRY= GL_GEOMETRY_SHADER,
		COMPUTE= GL_COMPUTE_SHADER
	};

	class ShaderProgram
	{
	private:
		GLuint program_ID_;
		bool linked_;
		std::map<string, int> uniform_locations_;

		GLint GetUniformLocation(const char* name);
		bool FileExists(const string& file_name);
		string GetExtension(const char* file_name);

		ShaderProgram();
		virtual ~ShaderProgram();

	public:
		

		virtual void CreateProgram();

		virtual void FreeProgram();

		void LoadShader(const char* file_name) throw (GLProgramException);
		void LoadShader(const char* file_name, GLShaderType type) throw (GLProgramException);
		void LoadShader(const string& source, GLShaderType type, const char* file_name= NULL) throw(GLProgramException);

		bool Bind();
		void UnBind();

		void Link();
		bool IsLinked();

		void Validate() throw(GLProgramException);

		void   BindAttribLocation( GLuint location, const char * name);
		void   BindFragDataLocation( GLuint location, const char * name );

		void   SetUniform( const char *name, float x, float y, float z);
		void   SetUniform( const char *name, const vec2 & v);
		void   SetUniform( const char *name, const vec3 & v);
		void   SetUniform( const char *name, const vec4 & v);
		void   SetUniform( const char *name, const mat4 & m);
		void   SetUniform( const char *name, const mat3 & m);
		void   SetUniform( const char *name, float val );
		void   SetUniform( const char *name, int val );
		void   SetUniform( const char *name, bool val );
		void   SetUniform( const char *name, GLuint val );

		void PrintActiveUniforms();
		void PrintActiveUniformBlocks();
		void PrintActiveAttribs();

		const char* GetTypeString(GLenum type);

		GLuint program_ID() {return program_ID_;}

		string GetProgramLog();
		string GetShaderLog(GLuint shader);
		
	};//class
};//namespace

#endif